using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;
using MacomberMap.Common.Types;

namespace MacomberMap.Common.Internals
{
    /// <summary>
    /// This class provides a dictionary whose elements can be accesed by index or by title.
    /// </summary>
    public class MM_Indexed_Dictionary<TKey, TValue>: IEnumerable<KeyValuePair<TKey,TValue>>
    {
        #region Variable declarations

        /// <summary>Our dictionary of components</summary>
        private Dictionary<TKey, TValue> ItemDictionary;

        /// <summary>Our list of components</summary>
        private Dictionary<int, TKey> ItemIndex;        
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new indexed dictionary
        /// </summary>
        public MM_Indexed_Dictionary()
        {            
            ItemDictionary = new Dictionary<TKey, TValue>();
            ItemIndex = new Dictionary<int, TKey>();
        }

        /// <summary>
        /// Initialize a new indexed dictionary
        /// </summary>
        /// <param name="comparer">The item comparer</param>
        public MM_Indexed_Dictionary(IEqualityComparer<TKey> comparer)
        {
            ItemDictionary = new Dictionary<TKey, TValue>(comparer);
            ItemIndex = new Dictionary<int, TKey>();
        }

        /// <summary>
        /// Initialize a new indexed dictionary
        /// </summary>
        /// <param name="comparer">The item comparer</param>
        /// <param name="Capacity">The capacity of the dictionary</param>
        public MM_Indexed_Dictionary(int Capacity, IEqualityComparer<TKey> comparer)
        {
            ItemDictionary = new Dictionary<TKey, TValue>(Capacity, comparer);
            ItemIndex = new Dictionary<int, TKey>();
        }


        /// <summary>
        /// Initialize a new indexed dictionary
        /// </summary>
        /// <param name="Capacity">The initial capacity of the list</param>
        public MM_Indexed_Dictionary(int Capacity)
        {
            ItemDictionary = new Dictionary<TKey, TValue>(Capacity);
            ItemIndex = new Dictionary<int, TKey>(Capacity);
        }
        #endregion

        #region Data Retrieval
        /// <summary>
        /// Retrieve an item by its index
        /// </summary>
        /// <param name="Index">The index to search for</param>
        /// <returns></returns>
        public TValue this[int Index]
        {
            get
            {
                return ItemDictionary[ItemIndex[Index]];
            }
        }

        /// <summary>
        /// Retrieve an item by its key
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public TValue this[TKey Key]
        {
            get
            {               
                    return ItemDictionary[Key];
            }
        }

        /// <summary>
        /// Return the key at a particular index
        /// </summary>
        /// <param name="Index"></param>
        /// <returns></returns>
        public TKey GetKeyAt(int Index)
        {
            return ItemIndex[Index];                
        }

        /// <summary>
        /// Try and get a value from the dictionary
        /// </summary>
        /// <param name="Key">The key to search for</param>
        /// <param name="Value">The value to pull out, if found</param>
        /// <returns>Whether the search was succesful.</returns>
        public bool TryGetValue(TKey Key, out TValue Value)
        {            
                return ItemDictionary.TryGetValue(Key, out Value);
        }
        
       
        /// <summary>
        /// Try and get a value from the specified index
        /// </summary>
        /// <param name="Key">The index to retrieve</param>
        /// <param name="Value">The value to retrieve</param>
        /// <returns></returns>
        public bool TryGetValue(int Key, out TValue Value)
        {
            TKey TargetKey;
            if (!ItemIndex.TryGetValue(Key, out TargetKey))
            {
                Value = default(TValue);
                return false;
            }
            return ItemDictionary.TryGetValue(TargetKey, out Value);
        }

        #endregion

        #region Hybrid lookup/addition
        /// <summary>
        /// Try and find an element within the dictionary. If one can't be found, add it in.
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public TValue Lookup(TKey Key)
        {
            TValue Value;
            lock (this)
                if (ItemDictionary.TryGetValue(Key, out Value))
                    return Value;
                else
                    return Add(Key);
        }
        #endregion
        

        #region Item removals
        /// <summary>
        /// Remove an element from the dictionary
        /// </summary>
        /// <param name="Key">The key to be removed</param>
        public void Remove(TKey Key)
        {
            lock (this)
            {
                ItemDictionary.Remove(Key);
                ItemIndex.Remove((int)Key.GetType().GetProperty("Index").GetValue(Key, null));
            }
        }

        /// <summary>
        /// Remove an element from the dictionary
        /// </summary>
        /// <param name="Index">The index of the item to be removed</param>
        public void Remove(int Index)
        {
            lock (this)
            {
                ItemDictionary.Remove(ItemIndex[Index]);
                ItemIndex.Remove(Index);
            }
        }

        #endregion

        #region Item additions

        /// <summary>
        /// Add a new key to the database, and determine the index from the count.
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public TValue Add(TKey Key)
        {
            lock (this)
            {
                int NewIndex = ItemIndex.Count;
                TValue OutIndex = (TValue)Convert.ChangeType(NewIndex, typeof(TValue));
                ItemDictionary.Add(Key, OutIndex);
                ItemIndex.Add(NewIndex, Key);
                return OutIndex;
            }
        }

        /// <summary>
        /// Add an element to the dictionary, and set that element's index property to the index.
        /// </summary>
        /// <param name="Key">The key to be added</param>
        /// <param name="Value">The value to be added</param>
        public void Add(TKey Key, TValue Value)
        {
            lock (this)
            {
                FieldInfo fI = Value.GetType().GetField("Index");
                int NewIndex = 0;
                if (fI != null)
                    NewIndex = (int)fI.GetValue(Value);

                if (NewIndex == 0)
                    NewIndex = ItemIndex.Count;
                while (ItemIndex.ContainsKey(NewIndex))
                    NewIndex++;

                ItemDictionary.Add(Key, Value);
                ItemIndex.Add(NewIndex, Key);

                if (fI != null)
                    fI.SetValue(Value, NewIndex);
            }
        }

        /// <summary>
        /// Add an element to the dictionary, and set that element's index property to the index.
        /// </summary>
        /// <param name="Index">The index of the item</param>
        /// <param name="Key">The key to be added</param>
        /// <param name="Value">The value to be added</param>
        public void Add(int Index, TKey Key, TValue Value)
        {
            ItemDictionary.Add(Key, Value);
            ItemIndex.Add(Index, Key);            
            foreach (MemberInfo mI in Value.GetType().GetMember("Index"))
                if (mI is FieldInfo)
                    (mI as FieldInfo).SetValue(Value,Index);
                else if (mI is PropertyInfo)
                    (mI as PropertyInfo).SetValue(Value,Index,null);            
        }
        #endregion

        #region Misc. functions
        
        /// <summary>
        /// Return the count of elements within the dictionary
        /// </summary>
        public int Count
        {
            get { return ItemDictionary.Count; }
        }

        /// <summary>
        /// Return the keys of the dictionary
        /// </summary>
        public Dictionary<TKey, TValue>.KeyCollection Keys
        {
            get { return ItemDictionary.Keys; }
        }

        /// <summary>
        /// Return the values of the dictionary
        /// </summary>
        public Dictionary<TKey, TValue>.ValueCollection Values
        {
            get { return ItemDictionary.Values; }
        }

        /// <summary>
        /// Return the indexes of the dictionary
        /// </summary>
        public ICollection<int> Indices
        {
            get { return ItemIndex.Keys; }
        }

        /// <summary>
        /// Determine whether the dictionary carries a specified key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool ContainsKey(TKey key)
        {
            return ItemDictionary.ContainsKey(key);
        }
        #endregion

        #region IEnumerable<KeyValuePair<TKey,TValue>> Members

        /// <summary>
        /// Return our enumerable collection
        /// </summary>
        /// <returns></returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return ItemDictionary.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ItemDictionary.GetEnumerator();
        }

        #endregion

        #region Clearing
        /// <summary>
        /// Clear all elements in the dictionary
        /// </summary>
        public void Clear()
        {
            ItemDictionary.Clear();
            ItemIndex.Clear();
        }
        #endregion
    }
}
